<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="前言本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。 组合模式组合模式就是用小的子对象来构建更大的对象，而这些小的子对象本身也许是由更 小的“孙对象”构成的。 我们在第 9 章命令模式中讲解过宏命令的结构和作用。宏命令对象包含了一组具体的子命令 对象，不管是宏命令对象，还是子命令对象，都有一个 execute 方法负责执行命令。现在">
<meta property="og:type" content="article">
<meta property="og:title" content="JavaScript的设计模式（三）">
<meta property="og:url" content="http://example.com/2021/11/03/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%89%EF%BC%89/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:description" content="前言本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。 组合模式组合模式就是用小的子对象来构建更大的对象，而这些小的子对象本身也许是由更 小的“孙对象”构成的。 我们在第 9 章命令模式中讲解过宏命令的结构和作用。宏命令对象包含了一组具体的子命令 对象，不管是宏命令对象，还是子命令对象，都有一个 execute 方法负责执行命令。现在">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-11-03T12:02:33.000Z">
<meta property="article:modified_time" content="2021-11-04T08:58:23.979Z">
<meta property="article:author" content="Li Yudong">
<meta property="article:tag" content="设计模式">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/2021/11/03/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%89%EF%BC%89/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>JavaScript的设计模式（三） | Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/03/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%89%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          JavaScript的设计模式（三）
        </h1>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-11-03 20:02:33" itemprop="dateCreated datePublished" datetime="2021-11-03T20:02:33+08:00">2021-11-03</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-04 16:58:23" itemprop="dateModified" datetime="2021-11-04T16:58:23+08:00">2021-11-04</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" itemprop="url" rel="index"><span itemprop="name">设计模式</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
        <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。</p>
<h1 id="组合模式"><a href="#组合模式" class="headerlink" title="组合模式"></a>组合模式</h1><p>组合模式就是用小的子对象来构建更大的对象，而这些小的子对象本身也许是由更 小的“孙对象”构成的。</p>
<p>我们在第 9 章命令模式中讲解过宏命令的结构和作用。宏命令对象包含了一组具体的子命令 对象，不管是宏命令对象，还是子命令对象，都有一个 execute 方法负责执行命令。现在回顾一 下这段安装在万能遥控器上的宏命令代码</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> closeDoorCommand = &#123; </span><br><span class="line">    execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'关门'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> openPcCommand = &#123; </span><br><span class="line">    execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'开电脑'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> openQQCommand = &#123; </span><br><span class="line">    execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'登录 QQ'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> MacroCommand = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        commandsList: [], </span><br><span class="line">        add: <span class="function"><span class="keyword">function</span>(<span class="params"> command </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">this</span>.commandsList.push( command ); </span><br><span class="line">        &#125;, </span><br><span class="line">        execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, command; command = <span class="keyword">this</span>.commandsList[ i++ ]; )&#123; </span><br><span class="line">                command.execute(); </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> macroCommand = MacroCommand(); </span><br><span class="line">macroCommand.add( closeDoorCommand ); </span><br><span class="line">macroCommand.add( openPcCommand ); </span><br><span class="line">macroCommand.add( openQQCommand ); </span><br><span class="line">macroCommand.execute();</span><br></pre></td></tr></table></figure>

<p>通过观察这段代码，我们很容易发现，宏命令中包含了一组子命令，它们组成了一个树形结 构，这里是一棵结构非常简单的树，</p>
<p>其中，marcoCommand 被称为组合对象，closeDoorCommand、openPcCommand、openQQCommand 都是 叶对象。在 macroCommand 的 execute 方法里，并不执行真正的操作，而是遍历它所包含的叶对象， 把真正的 execute 请求委托给这些叶对象。 macroCommand 表现得像一个命令，但它实际上只是一组真正命令的“代理”。并非真正的代理， 虽然结构上相似，但 macroCommand 只负责传递请求给叶对象，它的目的不在于控制对叶对象的访问。</p>
<p>组合模式将对象组合成树形结构，以表示“部分-整体”的层次结构。 除了用来表示树形结 构之外，组合模式的另一个好处是通过对象的多态性表现，使得用户对单个对象和组合对象的使 用具有一致性，下面分别说明。 </p>
<p>1、表示树形结构。通过回顾上面的例子，我们很容易找到组合模式的一个优点：提供了一 种遍历树形结构的方案，通过调用组合对象的 execute 方法，程序会递归调用组合对象下 面的叶对象的 execute 方法，所以我们的万能遥控器只需要一次操作，便能依次完成关门、 打开电脑、登录 QQ 这几件事情。组合模式可以非常方便地描述对象部分整体层次结构。</p>
<p>2、利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现，可以使客户端 忽略组合对象和单个对象的不同。在组合模式中，客户将统一地使用组合结构中的所有 对象，而不需要关心它究竟是组合对象还是单个对象。</p>
<p>请求从上到下沿着树进行传递，直到树的尽头。作为客户，只需要关心树最顶层的组合对象， 客户只需要请求这个组合对象，请求便会沿着树往下传递，依次到达所有的叶对象。</p>
<p>基本对象可以被组合成更复杂的组合对象，组合对象又可以被组合， 这样不断递归下去，这棵树的结构可以支持任意多的复杂度。在树最终被构造完成之后，让整颗 树最终运转起来的步骤非常简单，只需要调用最上层对象的 execute 方法。每当对最上层的对象 进行一次请求时，实际上是在对整个树进行深度优先的搜索，</p>
<h2 id="抽象类与透明性"><a href="#抽象类与透明性" class="headerlink" title="抽象类与透明性"></a>抽象类与透明性</h2><p>前面说到，组合模式最大的优点在于可以一致地对待组合对象和基本对象。客户不需要知道 当前处理的是宏命令还是普通命令，只要它是一个命令，并且有 execute 方法，这个命令就可以 被添加到树中。 这种透明性带来的便利，在静态类型语言中体现得尤为明显。</p>
<p>比如在 Java 中，实现组合模 式的关键是 Composite 类和 Leaf 类都必须继承自一个 Compenent 抽象类。这个 Compenent 抽象类既 代表组合对象，又代表叶对象，它也能够保证组合对象和叶对象拥有同样名字的方法，从而可以 对同一消息都做出反馈。组合对象和叶对象的具体类型被隐藏在 Compenent 抽象类身后。  </p>
<p>针对 Compenent 抽象类来编写程序，客户操作的始终是 Compenent 对象，而不用去区分到底是 组合对象还是叶对象。所以我们往同一个对象里的 add 方法里，既可以添加组合对象，也可以添 加叶对象，代码如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Java 代码 </span></span><br><span class="line">public abstract <span class="class"><span class="keyword">class</span> <span class="title">Component</span></span>&#123; </span><br><span class="line">    <span class="comment">//add 方法，参数为 Component 类型</span></span><br><span class="line">    public <span class="keyword">void</span> add( Component child )&#123;&#125; </span><br><span class="line">    <span class="comment">//remove 方法，参数为 Component 类型</span></span><br><span class="line">    public <span class="keyword">void</span> remove( Component child )&#123;&#125; </span><br><span class="line">&#125; </span><br><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">Composite</span> <span class="keyword">extends</span> <span class="title">Component</span></span>&#123; </span><br><span class="line">    <span class="comment">//add 方法，参数为 Component 类型</span></span><br><span class="line">    public <span class="keyword">void</span> add( Component child )&#123;&#125; </span><br><span class="line">    <span class="comment">//remove 方法，参数为 Component 类型</span></span><br><span class="line">    public <span class="keyword">void</span> remove( Component child )&#123;&#125; </span><br><span class="line">&#125; </span><br><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">Leaf</span> <span class="keyword">extends</span> <span class="title">Component</span></span>&#123; </span><br><span class="line">    <span class="comment">//add 方法，参数为 Component 类型</span></span><br><span class="line">    public <span class="keyword">void</span> add( Component child )&#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException() <span class="comment">// 叶对象不能再添加子节点</span></span><br><span class="line">    &#125; </span><br><span class="line"> <span class="comment">//remove 方法，参数为 Component 类型</span></span><br><span class="line">    public <span class="keyword">void</span> remove( Component child )&#123; </span><br><span class="line">    &#125; </span><br><span class="line">&#125; </span><br><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">client</span>()</span>&#123; </span><br><span class="line">    public <span class="keyword">static</span> <span class="keyword">void</span> main( <span class="built_in">String</span> args[] )&#123; </span><br><span class="line">        Component root = <span class="keyword">new</span> Composite(); </span><br><span class="line">        Component c1 = <span class="keyword">new</span> Composite(); </span><br><span class="line">        Component c2 = <span class="keyword">new</span> Composite(); </span><br><span class="line">        Component leaf1 = <span class="keyword">new</span> Leaf(); </span><br><span class="line">        Component leaf2 = <span class="keyword">new</span> Leaf(); </span><br><span class="line">        root.add(c1); </span><br><span class="line">        root.add(c2); </span><br><span class="line">        c1.add(leaf1); </span><br><span class="line">        c1.add(leaf2); </span><br><span class="line">        root.remove(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>而在 JavaScript 这种动态类型语言中，对象的多态性是与生俱来的，也没有编译器去检查 变量的类型，所以我们通常不会去模拟一个“怪异”的抽象类，JavaScript 中实现组合模式的难 点在于要保证组合对象和叶对象对象拥有同样的方法，这通常需要用鸭子类型的思想对它们进行 接口检查。 在 JavaScript 中实现组合模式，看起来缺乏一些严谨性，我们的代码算不上安全，但能更快 速和自由地开发，这既是 JavaScript 的缺点，也是它的优点。</p>
<p>组合模式的透明性使得发起请求的客户不用去顾忌树中组合对象和叶对象的区别，但它们在 本质上有是区别的。 组合对象可以拥有子节点，叶对象下面就没有子节点， 所以我们也许会发生一些误操作， 比如试图往叶对象中添加子节点。解决方案通常是给叶对象也增加 add 方法，并且在调用这个方 法时，抛出一个异常来及时提醒客户，</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> MacroCommand = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        commandsList: [], </span><br><span class="line">        add: <span class="function"><span class="keyword">function</span>(<span class="params"> command </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">this</span>.commandsList.push( command ); </span><br><span class="line">        &#125;, </span><br><span class="line">        execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, command; command = <span class="keyword">this</span>.commandsList[ i++ ]; )&#123; </span><br><span class="line">                command.execute(); </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> openTvCommand = &#123; </span><br><span class="line">    execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'打开电视'</span> ); </span><br><span class="line">    &#125;, </span><br><span class="line">    add: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'叶对象不能添加子节点'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> macroCommand = MacroCommand(); </span><br><span class="line">macroCommand.add( openTvCommand ); </span><br><span class="line">openTvCommand.add( macroCommand ) <span class="comment">// Uncaught Error: 叶对象不能添加子节点</span></span><br></pre></td></tr></table></figure>

<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>1、组合模式不是父子关系 组合模式的树型结构容易让人误以为组合对象和叶对象是父子关系，这是不正确的。 组合模式是一种 HAS-A（聚合）的关系，而不是 IS-A。组合对象包含一组叶对象，但 Leaf 并不是 Composite 的子类。组合对象把请求委托给它所包含的所有叶对象，它们能够合作的关键 是拥有相同的接口。  为了方便描述，本章有时候把上下级对象称为父子节点，但大家要知道，它们并非真正意义 上的父子关系。 </p>
<p>2、对叶对象操作的一致性 组合模式除了要求组合对象和叶对象拥有相同的接口之外，还有一个必要条件，就是对一组 叶对象的操作必须具有一致性。 比如公司要给全体员工发放元旦的过节费 1000 块，这个场景可以运用组合模式，但如果公 司给今天过生日的员工发送一封生日祝福的邮件，组合模式在这里就没有用武之地了，除非先把 今天过生日的员工挑选出来。只有用一致的方式对待列表中的每个叶对象的时候，才适合使用组 合模式。</p>
<p>3、双向映射关系 发放过节费的通知步骤是从公司到各个部门，再到各个小组，最后到每个员工的邮箱里。这 本身是一个组合模式的好例子，但要考虑的一种情况是，也许某些员工属于多个组织架构。比如 某位架构师既隶属于开发组，又隶属于架构组，对象之间的关系并不是严格意义上的层次结构， 在这种情况下，是不适合使用组合模式的，该架构师很可能会收到两份过节费。</p>
<p>这种复合情况下我们必须给父节点和子节点建立双向映射关系，一个简单的方法是给小组和员 工对象都增加集合来保存对方的引用。但是这种相互间的引用相当复杂，而且对象之间产生了过多 的耦合性，修改或者删除一个对象都变得困难，此时我们可以引入中介者模式来管理这些对象</p>
<p>4、 用职责链模式提高组合模式性能。在组合模式中，如果树的结构比较复杂，节点数量很多，在遍历树的过程中，性能方面也许表现得不够理想。有时候我们确实可以借助一些技巧，在实际操作中避免遍历整棵树，有一种现成的方案是借助职责链模式。职责链模式一般需要我们手动去设置链条，但在组合模式中，父对象和子对象之间实际上形成了天然的职责链。让请求顺着链条从父对象往子对象传递，或者是反过来从子对象往父对象传递，直到遇到可以处理该请求的对象为止，这也是职责链模式的经典运用场景之一</p>
<h2 id="引用父对象"><a href="#引用父对象" class="headerlink" title="引用父对象"></a>引用父对象</h2><p>组合对象保存了它下面的子节点的引用，这是组合模式的特点，此 时树结构是从上至下的。但有时候我们需要在子节点上保持对父节点的引用，比如在组合模式中 使用职责链时，有可能需要让请求从子节点往父节点上冒泡传递。还有当我们删除某个文件的时 候，实际上是从这个文件所在的上层文件夹中删除该文件的。  </p>
<p>现在来改写扫描文件夹的代码，使得在扫描整个文件夹之前，我们可以先移除某一个具体的 文件。 首先改写 Folder 类和 File 类，在这两个类的构造函数中，增加 this.parent 属性，并且在调 用 add 方法的时候，正确设置文件或者文件夹的父节点：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Folder = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.name = name; </span><br><span class="line">    <span class="keyword">this</span>.parent = <span class="literal">null</span>; <span class="comment">//增加 this.parent 属性</span></span><br><span class="line">    <span class="keyword">this</span>.files = []; </span><br><span class="line">&#125;; </span><br><span class="line">Folder.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"> file </span>)</span>&#123; </span><br><span class="line">    file.parent = <span class="keyword">this</span>; <span class="comment">//设置父对象</span></span><br><span class="line">    <span class="keyword">this</span>.files.push( file ); </span><br><span class="line">&#125;; </span><br><span class="line">Folder.prototype.scan = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'开始扫描文件夹: '</span> + <span class="keyword">this</span>.name ); </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file, files = <span class="keyword">this</span>.files; file = files[ i++ ]; )&#123; </span><br><span class="line">        file.scan(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//接下来增加 Folder.prototype.remove 方法，表示移除该文件夹</span></span><br><span class="line">Folder.prototype.remove = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( !<span class="keyword">this</span>.parent )&#123; <span class="comment">//根节点或者树外的游离节点</span></span><br><span class="line">        <span class="keyword">return</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> files = <span class="keyword">this</span>.parent.files, l = files.length - <span class="number">1</span>; l &gt;=<span class="number">0</span>; l-- )&#123; </span><br><span class="line">        <span class="keyword">var</span> file = files[ l ]; </span><br><span class="line">        <span class="keyword">if</span> ( file === <span class="keyword">this</span> )&#123; </span><br><span class="line">            files.splice( l, <span class="number">1</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">	在 File.prototype.remove 方法里，首先会判断 this.parent，如果 this.parent 为 null，那么这个文件夹要么是树的根节点，要么是还没有添加到树的游离节点，这时候没有节点需要从树中移除，我们暂且让 remove 方法直接 return，表示不做任何操作。</span></span><br><span class="line"><span class="comment">	如果 this.parent 不为 null，则说明该文件夹有父节点存在，此时遍历父节点中保存的子节点列表，删除想要删除的子节点。</span></span><br><span class="line"><span class="comment">File 类的实现基本一致</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> File = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.name = name; </span><br><span class="line">    <span class="keyword">this</span>.parent = <span class="literal">null</span>; </span><br><span class="line">&#125;; </span><br><span class="line">File.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'不能添加在文件下面'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">File.prototype.scan = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'开始扫描文件: '</span> + <span class="keyword">this</span>.name ); </span><br><span class="line">&#125;; </span><br><span class="line">File.prototype.remove = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( !<span class="keyword">this</span>.parent )&#123; <span class="comment">//根节点或者树外的游离节点</span></span><br><span class="line">        <span class="keyword">return</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> files = <span class="keyword">this</span>.parent.files, l = files.length - <span class="number">1</span>; l &gt;=<span class="number">0</span>; l-- )&#123; </span><br><span class="line">        <span class="keyword">var</span> file = files[ l ]; </span><br><span class="line">        <span class="keyword">if</span> ( file === <span class="keyword">this</span> )&#123; </span><br><span class="line">            files.splice( l, <span class="number">1</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//下面测试一下我们的移除文件功能：</span></span><br><span class="line"><span class="keyword">var</span> folder = <span class="keyword">new</span> Folder( <span class="string">'学习资料'</span> ); </span><br><span class="line"><span class="keyword">var</span> folder1 = <span class="keyword">new</span> Folder( <span class="string">'JavaScript'</span> ); </span><br><span class="line"><span class="keyword">var</span> file1 = <span class="keyword">new</span> Folder ( <span class="string">'深入浅出 Node.js'</span> ); </span><br><span class="line">folder1.add( <span class="keyword">new</span> File( <span class="string">'JavaScript 设计模式与开发实践'</span> ) ); </span><br><span class="line">folder.add( folder1 ); </span><br><span class="line">folder.add( file1 ); </span><br><span class="line">folder1.remove(); <span class="comment">//移除文件夹</span></span><br><span class="line">folder.scan();</span><br></pre></td></tr></table></figure>

<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>组合模式如果运用得当，可以大大简化客户的代码。一般来说，组合模式适用于以下这两种 情况。</p>
<p>1、表示对象的部分-整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是我们在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后，只需要通过请求树的最顶层对象，便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便，并且符合开放-封闭原则。</p>
<p>2、客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别， 客户在面对这棵树的时候，不用关心当前正在处理的对象是组合对象还是叶对象，也就 不用写一堆 if、else 语句来分别处理它们。组合对象和叶对象会各自做自己正确的事情， 这是组合模式最重要的能力。</p>
<p>组合模式可以让我们使用树形方式创 建对象的结构。我们可以把相同的操作应用在组合对象和单个对象上。在大多数情况下，我们都 可以忽略掉组合对象和单个对象之间的差别，从而用一致的方式来处理它们。 然而，组合模式并不是完美的，它可能会产生一个这样的系统：系统中的每个对象看起来都 与其他对象差不多。它们的区别只有在运行的时候会才会显现出来，这会使代码难以理解。此外， 如果通过组合模式创建了太多的对象，那么这些对象可能会让系统负担不起。</p>
<h1 id="模板方法模式"><a href="#模板方法模式" class="headerlink" title="模板方法模式"></a>模板方法模式</h1><p>在 JavaScript 开发中用到继承的场景其实并不是很多，很多时候我们都喜欢用 mix-in 的方式 给对象扩展属性。但这不代表继承在 JavaScript 里没有用武之地，虽然没有真正的类和继承机制， 但我们可以通过原型 prototype 来变相地实现继承。 不过本章并非要讨论继承，而是讨论一种基于继承的设计模式——模板方法（Template Method） 模式</p>
<p>模板方法模式是一种只需使用继承就可以实现的非常简单的模式。 模板方法模式由两部分结构组成，第一部分是抽象父类，第二部分是具体的实现子类。通常 在抽象父类中封装了子类的算法框架，包括实现一些公共方法以及封装子类中所有方法的执行顺 序。子类通过继承这个抽象类，也继承了整个算法结构，并且可以选择重写父类的方法。</p>
<h2 id="抽象类"><a href="#抽象类" class="headerlink" title="抽象类"></a>抽象类</h2><p>JavaScript 并没有从语法层面提供对抽象类的支持。抽象类的第一个作用是隐藏对象的具 体类型，由于 JavaScript 是一门“类型模糊”的语言，所以隐藏对象的类型在 JavaScript 中并 不重要。</p>
<p>另一方面，当我们在 JavaScript 中使用原型继承来模拟传统的类式继承时，并没有编译器帮 助我们进行任何形式的检查，我们也没有办法保证子类会重写父类中的“抽象方法”。</p>
<p>在 Java 中编译器会保证子类会重写父类中的抽象方法，但在 JavaScript 中却没有进行这些检 查工作。我们在编写代码的时候得不到任何形式的警告，完全寄托于程序员的记忆力和自觉性是 很危险的，特别是当我们使用模板方法模式这种完全依赖继承而实现的设计模式时。 下面提供两种变通的解决方案：</p>
<p>1、用鸭子类型来模拟接口检查，以便确保子类中确实重写了父类的方法。但模 拟接口检查会带来不必要的复杂性，而且要求程序员主动进行这些接口检查，这就要求 我们在业务代码中添加一些跟业务逻辑无关的代码</p>
<p>2、是让 Beverage.prototype.brew 等方法直接抛出一个异常，如果因为粗心忘记编 写 Coffee.prototype.brew 方法，那么至少我们会在程序运行时得到一个错误：</p>
<h2 id="钩子方法"><a href="#钩子方法" class="headerlink" title="钩子方法"></a>钩子方法</h2><p>通过模板方法模式，我们在父类中封装了子类的算法框架。这些算法框架在正常状态下是适 用于大多数子类的，但如果有一些特别“个性”的子类呢？</p>
<p>钩子方法（hook）可以用来解决这个问题，放置钩子是隔离变化的一种常见手段。我们在父 类中容易变化的地方放置钩子，钩子可以有一个默认的实现，究竟要不要“挂钩”，这由子类自行决定。钩子方法的返回结果决定了模板方法后面部分的执行步骤，也就是程序接下来的走向，这样一来，程序就拥有了变化的可能。 在这个例子里，我们把挂钩的名字定为 customerWantsCondiments，接下来将挂钩放入 Beverage 类，看看我们如何得到一杯不需要糖和牛奶的咖啡，代码如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Beverage = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;; </span><br><span class="line">Beverage.prototype.boilWater = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'把水煮沸'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">Beverage.prototype.brew = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'子类必须重写 brew 方法'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">Beverage.prototype.pourInCup = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'子类必须重写 pourInCup 方法'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">Beverage.prototype.addCondiments = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'子类必须重写 addCondiments 方法'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">Beverage.prototype.customerWantsCondiments = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 默认需要调料</span></span><br><span class="line">&#125;; </span><br><span class="line">Beverage.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.boilWater(); </span><br><span class="line">    <span class="keyword">this</span>.brew(); </span><br><span class="line">    <span class="keyword">this</span>.pourInCup(); </span><br><span class="line">    <span class="keyword">if</span> ( <span class="keyword">this</span>.customerWantsCondiments() )&#123; <span class="comment">// 如果挂钩返回 true，则需要调料</span></span><br><span class="line">        <span class="keyword">this</span>.addCondiments(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> CoffeeWithHook = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;; </span><br><span class="line">CoffeeWithHook.prototype = <span class="keyword">new</span> Beverage(); </span><br><span class="line">CoffeeWithHook.prototype.brew = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'用沸水冲泡咖啡'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">CoffeeWithHook.prototype.pourInCup = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'把咖啡倒进杯子'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">CoffeeWithHook.prototype.addCondiments = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'加糖和牛奶'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">CoffeeWithHook.prototype.customerWantsCondiments = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">window</span>.confirm( <span class="string">'请问需要调料吗？'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> coffeeWithHook = <span class="keyword">new</span> CoffeeWithHook(); </span><br><span class="line">coffeeWithHook.init();</span><br></pre></td></tr></table></figure>

<h2 id="好莱坞原则"><a href="#好莱坞原则" class="headerlink" title="好莱坞原则"></a>好莱坞原则</h2><p>学习完模板方法模式之后，我们要引入一个新的设计原则——著名的“好莱坞原则”。 </p>
<p>好莱坞无疑是演员的天堂，但好莱坞也有很多找不到工作的新人演员，许多新人演员在好莱 坞把简历递给演艺公司之后就只有回家等待电话。有时候该演员等得不耐烦了，给演艺公司打电 话询问情况，演艺公司往往这样回答：“不要来找我，我会给你打电话。” </p>
<p>在设计中，这样的规则就称为好莱坞原则。在这一原则的指导下，我们允许底层组件将自己 挂钩到高层组件中，而高层组件会决定什么时候、以何种方式去使用这些底层组件，高层组件对 待底层组件的方式，跟演艺公司对待新人演员一样，都是“别调用我们，我们会调用你”。 </p>
<p>模板方法模式是好莱坞原则的一个典型使用场景，它与好莱坞原则的联系非常明显，当我们 用模板方法模式编写一个程序时，就意味着子类放弃了对自己的控制权，而是改为父类通知子类， 哪些方法应该在什么时候被调用。作为子类，只负责提供一些设计上的细节。 除此之外，好莱坞原则还常常应用于其他模式和场景，例如发布-订阅模式和回调函数：</p>
<p>1、在发布—订阅模式中，发布者会把消息推送给订阅者，这取代了原先不断去 fetch 消息的形式。 例如假设我们乘坐出租车去一个不了解的地方，除了每过 5 秒钟就问司机“是否到达目的地”之 外，还可以在车上美美地睡上一觉，然后跟司机说好，等目的地到了就叫醒你。这也相当于好莱 坞原则中提到的“别调用我们，我们会调用你”。</p>
<p>2、在 ajax 异步请求中，由于不知道请求返回的具体时间，而通过轮询去判断是否返回数据，这 显然是不理智的行为。所以我们通常会把接下来的操作放在回调函数中，传入发起 ajax 异步请求 的函数。当数据返回之后，这个回调函数才被执行，这也是好莱坞原则的一种体现。把需要执行 的操作封装在回调函数里，然后把主动权交给另外一个函数。至于回调函数什么时候被执行，则 是另外一个函数控制的。</p>
<h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>模板方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。在传统的面向对象语 言中，一个运用了模板方法模式的程序中，子类的方法种类和执行顺序都是不变的，所以我们把 这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的，于是我们把这 部分变化的逻辑封装到子类中。通过增加新的子类，我们便能给系统增加新的功能，并不需要改 动抽象父类以及其他子类，这也是符合开放封闭原则的。 </p>
<p>但在 JavaScript 中，我们很多时候都不需要依样画瓢地去实现一个模版方法模式，高阶函数（一个函数可以接收另一个函数做参数，该函数就可以称为高阶函数）是更好的选择。如下代码所示：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Beverage = <span class="function"><span class="keyword">function</span>(<span class="params"> param </span>)</span>&#123;</span><br><span class="line">    <span class="keyword">var</span> boilWater = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'把水煮沸'</span> ); </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> brew = param.brew || <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'必须传递 brew 方法'</span> ); </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> pourInCup = param.pourInCup || <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'必须传递 pourInCup 方法'</span> ); </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> addCondiments = param.addCondiments || <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'必须传递 addCondiments 方法'</span> ); </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> F = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;; </span><br><span class="line">    F.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        boilWater(); </span><br><span class="line">        brew(); </span><br><span class="line">        pourInCup(); </span><br><span class="line">        addCondiments(); </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">return</span> F;</span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> Coffee = Beverage(&#123; </span><br><span class="line">    brew: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'用沸水冲泡咖啡'</span> ); </span><br><span class="line">    &#125;, </span><br><span class="line">    pourInCup: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'把咖啡倒进杯子'</span> ); </span><br><span class="line">    &#125;, </span><br><span class="line">    addCondiments: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'加糖和牛奶'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;); </span><br><span class="line"><span class="keyword">var</span> Tea = Beverage(&#123;</span><br><span class="line">    brew: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'用沸水浸泡茶叶'</span> ); </span><br><span class="line">    &#125;, </span><br><span class="line">    pourInCup: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'把茶倒进杯子'</span> ); </span><br><span class="line">    &#125;, </span><br><span class="line">    addCondiments: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'加柠檬'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;); </span><br><span class="line"><span class="keyword">var</span> coffee = <span class="keyword">new</span> Coffee(); </span><br><span class="line">coffee.init(); </span><br><span class="line"><span class="keyword">var</span> tea = <span class="keyword">new</span> Tea(); </span><br><span class="line">tea.init();</span><br></pre></td></tr></table></figure>

<p>在这段代码中，我们把 brew、pourInCup、addCondiments 这些方法依次传入 Beverage 函数， Beverage 函数被调用之后返回构造器 F。F 类中包含了“模板方法”F.prototype.init。跟继承得到的效果一样，该“模板方法”里依然封装了饮料子类的算法框架</p>
<h1 id="享元模式"><a href="#享元模式" class="headerlink" title="享元模式"></a>享元模式</h1><p>享元（flyweight）模式是一种用于性能优化的模式，“fly”在这里是苍蝇的意思，意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。 如果系统中因为创建了大量类似的对象而导致内存占用过高，享元模式就非常有用了。在 JavaScript 中，浏览器特别是移动端的浏览器分配的内存并不算多，如何节省内存就成了一件非 常有意义的事情。 享元模式的概念初听起来并不太好理解，所以在深入讲解之前，我们先看一个例子。</p>
<p>假设有个内衣工厂，目前的产品有 50 种男式内衣和 50 种女士内衣，为了推销产品，工厂决 定生产一些塑料模特来穿上他们的内衣拍成广告照片。 正常情况下需要 50 个男模特和 50 个女 模特，然后让他们每人分别穿上一件内衣来拍照。</p>
<p>要得到一张照片，每次都需要传入 sex 和 underwear 参数，如上所述，现在一共有 50 种男内 衣和 50 种女内衣，所以一共会产生 100 个对象。如果将来生产了 10000 种内衣，那这个程序可 能会因为存在如此多的对象已经提前崩溃。 下面我们来考虑一下如何优化这个场景。虽然有 100 种内衣，但很显然并不需要 50 个男 模特和 50 个女模特。其实男模特和女模特各自有一个就足够了，他们可以分别穿上不同的内衣来拍照。现在来改写一下代码，既然只需要区别男女模特，那我们先把 underwear 参数从构造函数中移除，构造函数只接收 sex 参数：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Model = <span class="function"><span class="keyword">function</span>(<span class="params"> sex </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.sex = sex; </span><br><span class="line">&#125;; </span><br><span class="line">Model.prototype.takePhoto = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'sex= '</span> + <span class="keyword">this</span>.sex + <span class="string">' underwear='</span> + <span class="keyword">this</span>.underwear); </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> maleModel = <span class="keyword">new</span> Model( <span class="string">'male'</span> ), </span><br><span class="line">    femaleModel = <span class="keyword">new</span> Model( <span class="string">'female'</span> ); </span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">1</span>; i &lt;= <span class="number">50</span>; i++ )&#123; </span><br><span class="line">    maleModel.underwear = <span class="string">'underwear'</span> + i; </span><br><span class="line">    maleModel.takePhoto(); </span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> j = <span class="number">1</span>; j &lt;= <span class="number">50</span>; j++ )&#123; </span><br><span class="line">    femaleModel.underwear = <span class="string">'underwear'</span> + j; </span><br><span class="line">    femaleModel.takePhoto(); </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>享元模式要求将对象的属性划分为内部状态与外部 状态（状态在这里通常指属性）。享元模式的目标是尽量减少共享对象的数量，关于如何划分内 部状态和外部状态，</p>
<p>剥离了外部状态的对象成为共享对象，外部状态在必要时被传入共享对象来组装成一个完整 的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间，但却可以大大减少系 统中的对象数量，相比之下，这点时间或许是微不足道的。因此，享元模式是一种用时间换空间 的优化模式。</p>
<h2 id="通用结构"><a href="#通用结构" class="headerlink" title="通用结构"></a>通用结构</h2><p>我们通过构造函数显式 new 出了男女两个 model 对象，在其他系统中，也许并不是一开始 就需要所有的共享对象。 给 model 对象手动设置了 underwear 外部状态，在更复杂的系统中，这不是一个最好的方 式，因为外部状态可能会相当复杂，它们与共享对象的联系会变得困难。 </p>
<p>我们通过一个对象工厂来解决第一个问题，只有当某种共享对象被真正需要时，它才从工厂 中被创建出来。对于第二个问题，可以用一个管理器来记录对象相关的外部状态，使这些外部状 态通过某个钩子和共享对象联系起来。</p>
<h2 id="文件上传"><a href="#文件上传" class="headerlink" title="文件上传"></a>文件上传</h2><p>文件上传功能虽然可以选 择依照队列，一个一个地排队上传，但也支持同时选择 2000 个文件。每一个文件都对应着一个 JavaScript 上传对象的创建，在第一版开发中，的确往程序里同时 new 了 2000 个 upload 对象，结 果可想而知，Chrome 中还勉强能够支撑，IE 下直接进入假死状态。 项目支持好几种上传方式，比如浏览器插件、Flash 和表单上传等，为了简化例子，我们先 假设只有插件和 Flash 这两种。</p>
<p>基本的代码上传逻辑如下：当用户选择了文件之后，插件和 Flash 都会通知调用 Window 下的一个全局 JavaScript 函数，它的名字是 startUpload，用户选择的文件列表被组合成一个数组 files 塞进该函数的参数列表里，代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> id = <span class="number">0</span>; </span><br><span class="line"><span class="built_in">window</span>.startUpload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType, files </span>)</span>&#123; <span class="comment">// uploadType 区分是控件还是 flash </span></span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file; file = files[ i++ ]; )&#123; </span><br><span class="line">        <span class="keyword">var</span> uploadObj = <span class="keyword">new</span> Upload( uploadType, file.fileName, file.fileSize ); </span><br><span class="line">        uploadObj.init( id++ ); <span class="comment">// 给 upload 对象设置一个唯一的 id </span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">当用户选择完文件之后，startUpload 函数会遍历 files 数组来创建对应的 upload 对象。接下</span></span><br><span class="line"><span class="comment">来定义 Upload 构造函数，它接受 3 个参数，分别是插件类型、文件名和文件大小。这些信息都已</span></span><br><span class="line"><span class="comment">经被插件组装在 files 数组里返回，代码如下：</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> Upload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType, fileName, fileSize </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.uploadType = uploadType; </span><br><span class="line">    <span class="keyword">this</span>.fileName = fileName; </span><br><span class="line">    <span class="keyword">this</span>.fileSize = fileSize; </span><br><span class="line">    <span class="keyword">this</span>.dom= <span class="literal">null</span>; </span><br><span class="line">&#125;; </span><br><span class="line">Upload.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> that = <span class="keyword">this</span>; </span><br><span class="line">    <span class="keyword">this</span>.id = id; </span><br><span class="line">    <span class="keyword">this</span>.dom = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> ); </span><br><span class="line">    <span class="keyword">this</span>.dom.innerHTML = </span><br><span class="line"> <span class="string">'&lt;span&gt;文件名称:'</span>+ <span class="keyword">this</span>.fileName +<span class="string">', 文件大小: '</span>+ <span class="keyword">this</span>.fileSize +<span class="string">'&lt;/span&gt;'</span> + </span><br><span class="line"> <span class="string">'&lt;button class="delFile"&gt;删除&lt;/button&gt;'</span>; </span><br><span class="line">    <span class="keyword">this</span>.dom.querySelector( <span class="string">'.delFile'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        that.delFile(); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="built_in">document</span>.body.appendChild( <span class="keyword">this</span>.dom ); </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">同样为了简化示例，我们暂且去掉了 upload 对象的其他功能，只保留删除文件的功能，对应</span></span><br><span class="line"><span class="comment">的方法是 Upload.prototype.delFile。该方法中有一个逻辑：当被删除的文件小于 3000 KB 时，该文件将被直接删除。否则页面中会弹出一个提示框，提示用户是否确认要删除该文件。</span></span><br><span class="line"><span class="comment">接下来分别创建 3 个插件上传对象和 3 个 Flash 上传对象：</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">Upload.prototype.delFile = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( <span class="keyword">this</span>.fileSize &lt; <span class="number">3000</span> )&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom ); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span> ( <span class="built_in">window</span>.confirm( <span class="string">'确定要删除该文件吗? '</span> + <span class="keyword">this</span>.fileName ) )&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line">startUpload( <span class="string">'plugin'</span>, [ </span><br><span class="line">    &#123; </span><br><span class="line">        fileName: <span class="string">'1.txt'</span>, </span><br><span class="line">        fileSize: <span class="number">1000</span> </span><br><span class="line">    &#125;, </span><br><span class="line">    &#123; </span><br><span class="line">        fileName: <span class="string">'2.html'</span>, </span><br><span class="line">        fileSize: <span class="number">3000</span> </span><br><span class="line">    &#125;, </span><br><span class="line">    &#123; </span><br><span class="line">        fileName: <span class="string">'3.txt'</span>, </span><br><span class="line">        fileSize: <span class="number">5000</span> </span><br><span class="line">    &#125; </span><br><span class="line">]); </span><br><span class="line">startUpload( <span class="string">'flash'</span>, [ </span><br><span class="line">    &#123; </span><br><span class="line">        fileName: <span class="string">'4.txt'</span>, </span><br><span class="line">        fileSize: <span class="number">1000</span> </span><br><span class="line">    &#125;, </span><br><span class="line">    &#123; </span><br><span class="line">        fileName: <span class="string">'5.html'</span>, </span><br><span class="line">        fileSize: <span class="number">3000</span> </span><br><span class="line">    &#125;, </span><br><span class="line">    &#123; </span><br><span class="line">        fileName: <span class="string">'6.txt'</span>, </span><br><span class="line">        fileSize: <span class="number">5000</span> </span><br><span class="line">    &#125; </span><br><span class="line">]);</span><br></pre></td></tr></table></figure>

<h3 id="享元重构"><a href="#享元重构" class="headerlink" title="享元重构"></a>享元重构</h3><p>来我们用享元模式重构它。 首先，我们需要确认插件类型 uploadType 是内部状态，那为什么单单 uploadType 是内部状态 呢？前面讲过，划分内部状态和外部状态的关键主要有以下几点。 </p>
<p>1、 内部状态储存于对象内部。 2、内部状态可以被一些对象共享。 3、内部状态独立于具体的场景，通常不会改变。4、外部状态取决于具体的场景，并根据场景而变化，外部状态不能被共享。</p>
<p>在文件上传的例子里，upload 对象必须依赖 uploadType 属性才能工作，这是因为插件上传、 Flash 上传、表单上传的实际工作原理有很大的区别，它们各自调用的接口也是完全不一样的， 必须在对象创建之初就明确它是什么类型的插件，才可以在程序的运行过程中，让它们分别调用 各自的 start、pause、cancel、del 等方法。 实际上在微云的真实代码中，虽然插件和 Flash 上传对象最终创建自一个大的工厂类，但它们实际上根据 uploadType 值的不同，分别是来自于两个不同类的对象。（在目前的例子中，为了 简化代码，我们把插件和 Flash 的构造函数合并成了一个。） 一旦明确了 uploadType，无论我们使用什么方式上传，这个上传对象都是可以被任何文件共 用的。而 fileName 和 fileSize 是根据场景而变化的，每个文件的 fileName 和 fileSize 都不一样， fileName 和 fileSize 没有办法被共享，它们只能被划分为外部状态。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*明确了 uploadType 作为内部状态之后，我们再把其他的外部状态从构造函数中抽离出来，</span></span><br><span class="line"><span class="comment">Upload 构造函数中只保留 uploadType 参数：*/</span></span><br><span class="line"><span class="keyword">var</span> Upload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType</span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.uploadType = uploadType; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">/*Upload.prototype.init函数也不再需要，因为upload对象初始化的工作被放在了uploadManager.add 函数里面，接下来只需要定义 Upload.prototype.del 函数即可*/</span></span><br><span class="line">Upload.prototype.delFile = <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>&#123; </span><br><span class="line">    uploadManager.setExternalState( id, <span class="keyword">this</span> ); <span class="comment">// (1) </span></span><br><span class="line">    <span class="keyword">if</span> ( <span class="keyword">this</span>.fileSize &lt; <span class="number">3000</span> )&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom ); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span> ( <span class="built_in">window</span>.confirm( <span class="string">'确定要删除该文件吗? '</span> + <span class="keyword">this</span>.fileName ) )&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">在开始删除文件之前，需要读取文件的实际大小，而文件的实际大小被储存在外部管理器</span></span><br><span class="line"><span class="comment">uploadManager 中，所以在这里需要通过 uploadManager.setExternalState 方法给共享对象设置正确</span></span><br><span class="line"><span class="comment">的 fileSize，上段代码中的(1)处表示把当前 id 对应的对象的外部状态都组装到共享对象中。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="comment">//接下来定义一个工厂来创建 upload 对象，如果某种内部状态对应的共享对象已经被创建过，那么直接返回这个对象，否则创建一个新的对象；这里的UploadFactory定义为自执行函数的返回值，匿名自执行函数是为了模拟块级作用域。</span></span><br><span class="line"><span class="keyword">var</span> UploadFactory = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> createdFlyWeightObjs = &#123;&#125;; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        create: <span class="function"><span class="keyword">function</span>(<span class="params">uploadType</span>)</span>&#123; </span><br><span class="line">            <span class="keyword">if</span> ( createdFlyWeightObjs [uploadType] )&#123; </span><br><span class="line">                <span class="keyword">return</span> createdFlyWeightObjs [uploadType]; </span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">return</span> createdFlyWeightObjs [uploadType] = <span class="keyword">new</span> Upload(uploadType); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)();</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">现在我们来完善前面提到的 uploadManager 对象，它负责向 UploadFactory 提交创建对象的请</span></span><br><span class="line"><span class="comment">求，并用一个 uploadDatabase 对象保存所有 upload 对象的外部状态，以便在程序运行过程中给</span></span><br><span class="line"><span class="comment">upload 共享对象设置外部状态，</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> uploadManager = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> uploadDatabase = &#123;&#125;; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        add: <span class="function"><span class="keyword">function</span>(<span class="params"> id, uploadType, fileName, fileSize </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> flyWeightObj = UploadFactory.create( uploadType ); </span><br><span class="line">            <span class="keyword">var</span> dom = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> ); </span><br><span class="line">            dom.innerHTML = </span><br><span class="line"> <span class="string">'&lt;span&gt;文件名称:'</span>+ fileName +<span class="string">', 文件大小: '</span>+ fileSize +<span class="string">'&lt;/span&gt;'</span> + </span><br><span class="line"> <span class="string">'&lt;button class="delFile"&gt;删除&lt;/button&gt;'</span>; </span><br><span class="line">            dom.querySelector( <span class="string">'.delFile'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                flyWeightObj.delFile( id ); </span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">document</span>.body.appendChild( dom ); </span><br><span class="line">            uploadDatabase[ id ] = &#123; </span><br><span class="line">                fileName: fileName, </span><br><span class="line">                fileSize: fileSize, </span><br><span class="line">                dom: dom </span><br><span class="line">            &#125;; </span><br><span class="line">            <span class="keyword">return</span> flyWeightObj ; </span><br><span class="line">        &#125;, </span><br><span class="line">        setExternalState: <span class="function"><span class="keyword">function</span>(<span class="params"> id, flyWeightObj </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> uploadData = uploadDatabase[ id ]; </span><br><span class="line">            <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> uploadData )&#123; </span><br><span class="line">                flyWeightObj[ i ] = uploadData[ i ]; </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line"><span class="comment">//然后是开始触发上传动作的 startUpload 函数：</span></span><br><span class="line"><span class="keyword">var</span> id = <span class="number">0</span>; </span><br><span class="line"><span class="built_in">window</span>.startUpload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType, files </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file; file = files[ i++ ]; )&#123; </span><br><span class="line">        <span class="keyword">var</span> uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="适用性"><a href="#适用性" class="headerlink" title="适用性"></a>适用性</h2><p>享元模式是一种很好的性能优化方案，但它也会带来一些复杂性的问题，从前面两组代码的 比较可以看到，使用了享元模式之后，我们需要分别多维护一个 factory 对象和一个 manager 对 象，在大部分不必要使用享元模式的环境下，这些开销是可以避免的。 享元模式带来的好处很大程度上取决于如何使用以及何时使用，一般来说，以下情况发生时 便可以使用享元模式</p>
<p>1、 一个程序中使用了大量的相似对象。 2、 由于使用了大量对象，造成很大的内存开销。 3、 对象的大多数状态都可以变为外部状态。 4、 剥离出对象的外部状态之后，可以用相对较少的共享对象取代大量对象</p>
<p>可以看到，文件上传的例子完全符合这四点。</p>
<p>我们知道，实现享元模式的关键是把内部状态和外部状态分离开来。有多少种内部状 态的组合，系统中便最多存在多少个共享对象，而外部状态储存在共享对象的外部，在必要时被 传入共享对象来组装成一个完整的对象。现在来考虑两种极端的情况，即对象没有外部状态和没有内部状态的时候。</p>
<p>没有内部状态时：构造函数 Upload 就变成了无参数的形式；其他属性如 fileName、fileSize、dom 依然可以作为外部状态保存在共享对象外部。在 uploadType 作为内部状态的时候，它可能为控件，也可能为 Flash，所以当时最多可以组合出两个 共享对象。而现在已经没有了内部状态，这意味着只需要唯一的一个共享对象。</p>
<p>管理器部分的代码不需要改动，还是负责剥离和组装外部状态。可以看到，当对象没有内部 状态的时候，生产共享对象的工厂实际上变成了一个单例工厂。虽然这时候的共享对象没有内部 状态的区分，但还是有剥离外部状态的过程，我们依然倾向于称之为享元模式</p>
<p>没有外部状态时：没有外部状态的剥离，只有共享则不能称为享元模式。</p>
<h2 id="对象池"><a href="#对象池" class="headerlink" title="对象池"></a>对象池</h2><p>对象池维护一个装载空闲对象的池子，如果需要对象的时候，不是直接 new，而是转从对象池里获取。如 果对象池里没有空闲对象，则创建一个新的对象，当获取出的对象完成它的职责之后， 再进入 池子等待被下次获取。 对象池的原理很好理解，比如我们组人手一本《JavaScript 权威指南》，从节约的角度来讲， 这并不是很划算，因为大部分时间这些书都被闲置在各自的书架上，所以我们一开始就只买一本， 或者一起建立一个小型图书馆（对象池），需要看书的时候就从图书馆里借，看完了之后再把书 还回图书馆。</p>
<p>如果同时有三个人要看这本书，而现在图书馆里只有两本，那我们再马上去书店买 一本放入图书馆。对象池技术的应用非常广泛，<strong>HTTP 连接池和数据库连接池</strong>都是其代表应用。在 Web 前端开 发中，对象池使用最多的场景大概就是跟 DOM 有关的操作。很多空间和时间都消耗在了 DOM 节点上，如何避免频繁地创建和删除 DOM 节点就成了一个有意义的话题。</p>
<p>我们可以在对象池工厂里，把创建对象的具体过程封装起来，实现一个通用的对象池：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> objectPoolFactory = <span class="function"><span class="keyword">function</span>(<span class="params"> createObjFn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> objectPool = []; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        create: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> obj = objectPool.length === <span class="number">0</span> ? </span><br><span class="line">                createObjFn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ) : objectPool.shift(); </span><br><span class="line">            <span class="keyword">return</span> obj; </span><br><span class="line">        &#125;, </span><br><span class="line">        recover: <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>&#123; </span><br><span class="line">            objectPool.push( obj );</span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">//现在利用 objectPoolFactory 来创建一个装载一些 iframe 的对象池：</span></span><br><span class="line"><span class="keyword">var</span> iframeFactory = objectPoolFactory( <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> iframe = <span class="built_in">document</span>.createElement( <span class="string">'iframe'</span> ); </span><br><span class="line">    <span class="built_in">document</span>.body.appendChild( iframe ); </span><br><span class="line">    iframe.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        iframe.onload = <span class="literal">null</span>; <span class="comment">// 防止 iframe 重复加载的 bug </span></span><br><span class="line">        iframeFactory.recover( iframe ); <span class="comment">// iframe 加载完成之后回收节点</span></span><br><span class="line"> &#125; </span><br><span class="line">    <span class="keyword">return</span> iframe; </span><br><span class="line">&#125;); </span><br><span class="line"><span class="keyword">var</span> iframe1 = iframeFactory.create(); </span><br><span class="line">iframe1.src = <span class="string">'http:// baidu.com'</span>; </span><br><span class="line"><span class="keyword">var</span> iframe2 = iframeFactory.create(); </span><br><span class="line">iframe2.src = <span class="string">'http:// QQ.com'</span>; </span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> iframe3 = iframeFactory.create(); </span><br><span class="line">    iframe3.src = <span class="string">'http:// 163.com'</span>; </span><br><span class="line">&#125;, <span class="number">3000</span> );</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">对象池是另外一种性能优化方案，它跟享元模式有一些相似之处，但没有分离内部状态和外</span></span><br><span class="line"><span class="comment">部状态这个过程。本章用享元模式完成了一个文件上传的程序，其实也可以用对象池+事件委托来代替实现</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>享元模式是为解决性能问题而生的模式，这跟大部分模式的诞生原因都不一样。在一个存在 大量相似对象的系统中，享元模式可以很好地解决大量对象带来的性能问题。</p>
<h1 id="职责链模式"><a href="#职责链模式" class="headerlink" title="职责链模式"></a>职责链模式</h1><p>职责链模式的定义是：使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间 的耦合关系，将这些对象连成一条链，并沿着这条链传递该请求，直到有一个对象处理它为止。 职责链模式的名字非常形象，一系列可能会处理请求的对象被连接成一条链，请求在这些对 象之间依次传递，直到遇到一个可以处理它的对象，我们把这些对象称为链中的节点，</p>
<p>职责链模式的最大优点：请求发送者只需要知道链中的第 一个节点，从而弱化了发送者和一组接收者之间的强联系。</p>
<h2 id="简单实例"><a href="#简单实例" class="headerlink" title="简单实例"></a>简单实例</h2><p>假设我们负责一个售卖手机的电商网站，经过分别交纳 500 元定金和 200 元定金的两轮预定 后（订单已在此时生成），现在已经到了正式购买的阶段。 公司针对支付过定金的用户有一定的优惠政策。在正式购买后，已经支付过 500 元定金的用 户会收到 100 元的商城优惠券，200 元定金的用户可以收到 50 元的优惠券，而之前没有支付定金 的用户只能进入普通购买模式，也就是没有优惠券，且在库存有限的情况下不一定保证能买到。 我们的订单页面是 PHP 吐出的模板，在页面加载之初，PHP 会传递给页面几个字段。 </p>
<p>1、orderType：表示订单类型（定金用户或者普通购买用户），code 的值为 1 的时候是 500 元 定金用户，为 2 的时候是 200 元定金用户，为 3 的时候是普通购买用户。2、 pay：表示用户是否已经支付定金，值为 true 或者 false, 虽然用户已经下过 500 元定金的 订单，但如果他一直没有支付定金，现在只能降级进入普通购买模式。3、stock：表示当前用于普通购买的手机库存数量，已经支付过 500 元或者 200 元定金的用 户不受此限制。 下面我们把这个流程写成代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> order = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( orderType === <span class="number">1</span> )&#123; <span class="comment">// 500 元定金购买模式</span></span><br><span class="line">        <span class="keyword">if</span> ( pay === <span class="literal">true</span> )&#123; <span class="comment">// 已支付定金</span></span><br><span class="line">            <span class="built_in">console</span>.log( <span class="string">'500 元定金预购, 得到 100 优惠券'</span> ); </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123; <span class="comment">// 未支付定金，降级到普通购买模式</span></span><br><span class="line">            <span class="keyword">if</span> ( stock &gt; <span class="number">0</span> )&#123; <span class="comment">// 用于普通购买的手机还有库存</span></span><br><span class="line">                <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> ); </span><br><span class="line">            &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">                <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> ); </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ( orderType === <span class="number">2</span> )&#123; <span class="comment">// 200 元定金购买模式</span></span><br><span class="line">        <span class="keyword">if</span> ( pay === <span class="literal">true</span> )&#123; </span><br><span class="line">            <span class="built_in">console</span>.log( <span class="string">'200 元定金预购, 得到 50 优惠券'</span> ); </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">            <span class="keyword">if</span> ( stock &gt; <span class="number">0</span> )&#123; </span><br><span class="line">                <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> ); </span><br><span class="line">            &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">                <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> ); </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ( orderType === <span class="number">3</span> )&#123; </span><br><span class="line">        <span class="keyword">if</span> ( stock &gt; <span class="number">0</span> )&#123; </span><br><span class="line">            <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> ); </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">            <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line">order( <span class="number">1</span> , <span class="literal">true</span>, <span class="number">500</span>); <span class="comment">// 输出： 500 元定金预购, 得到 100 优惠券</span></span><br></pre></td></tr></table></figure>

<p>上面的代码虽然实现了功能，但是乱七八糟且难以后续维护与功能的迭代、更新。</p>
<h3 id="重构"><a href="#重构" class="headerlink" title="重构"></a>重构</h3><p>现在我们采用职责链模式重构这段代码，先把 500 元订单、200 元订单以及普通购买分成 3 个函数。 接下来把 orderType、pay、stock 这 3 个字段当作参数传递给 500 元订单函数，如果该函数不 符合处理条件，则把这个请求传递给后面的 200 元订单函数，如果 200 元订单函数依然不能处理 该请求，则继续传递请求给普通购买函数，代码如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 500 元订单</span></span><br><span class="line"><span class="keyword">var</span> order500 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( orderType === <span class="number">1</span> &amp;&amp; pay === <span class="literal">true</span> )&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'500 元定金预购, 得到 100 优惠券'</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        order200( orderType, pay, stock ); <span class="comment">// 将请求传递给 200 元订单</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">// 200 元订单</span></span><br><span class="line"><span class="keyword">var</span> order200 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( orderType === <span class="number">2</span> &amp;&amp; pay === <span class="literal">true</span> )&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'200 元定金预购, 得到 50 优惠券'</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        orderNormal( orderType, pay, stock ); <span class="comment">// 将请求传递给普通订单</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">// 普通购买订单</span></span><br><span class="line"><span class="keyword">var</span> orderNormal = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( stock &gt; <span class="number">0</span> )&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">// 测试结果：</span></span><br><span class="line">order500( <span class="number">1</span> , <span class="literal">true</span>, <span class="number">500</span>); <span class="comment">// 输出：500 元定金预购, 得到 100 优惠券</span></span><br><span class="line">order500( <span class="number">1</span>, <span class="literal">false</span>, <span class="number">500</span> ); <span class="comment">// 输出：普通购买, 无优惠券</span></span><br><span class="line">order500( <span class="number">2</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出：200 元定金预购, 得到 500 优惠券</span></span><br><span class="line">order500( <span class="number">3</span>, <span class="literal">false</span>, <span class="number">500</span> ); <span class="comment">// 输出：普通购买, 无优惠券</span></span><br><span class="line">order500( <span class="number">3</span>, <span class="literal">false</span>, <span class="number">0</span> ); <span class="comment">// 输出：手机库存不足</span></span><br></pre></td></tr></table></figure>

<p>可以看到，执行结果和前面那个巨大的 order 函数完全一样，但是代码的结构已经清晰了很 多，我们把一个大函数拆分了 3 个小函数，去掉了许多嵌套的条件分支语句。 目前已经有了不小的进步，但我们不会满足于此，虽然已经把大函数拆分成了互不影响的 3 个小函数，但可以看到，请求在链条传递中的顺序非常僵硬，传递请求的代码被耦合在了业务函 数之中。</p>
<p>这依然是违反开放封闭原则的，如果有天我们要增加 300 元预订或者去掉 200 元预订，意 味着就必须改动这些业务函数内部。就像一根环环相扣打了死结的链条，如果要增加、拆除或者 移动一个节点，就必须得先砸烂这根链条。</p>
<h3 id="可拆分节点"><a href="#可拆分节点" class="headerlink" title="可拆分节点"></a>可拆分节点</h3><p>本节我们采用一种更灵活的方式，来改进上面的职责链模式，目标是让链中的各个节点可以 灵活拆分和重组。 首先需要改写一下分别表示 3 种购买模式的节点函数，我们约定，如果某个节点不能处理请 求，则返回一个特定的字符串 ‘nextSuccessor’来表示该请求需要继续往后面传递：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> order500 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( orderType === <span class="number">1</span> &amp;&amp; pay === <span class="literal">true</span> )&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'500 元定金预购，得到 100 优惠券'</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="string">'nextSuccessor'</span>; <span class="comment">// 我不知道下一个节点是谁，反正把请求往后面传递</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> order200 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( orderType === <span class="number">2</span> &amp;&amp; pay === <span class="literal">true</span> )&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'200 元定金预购，得到 50 优惠券'</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="string">'nextSuccessor'</span>; <span class="comment">// 我不知道下一个节点是谁，反正把请求往后面传递</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> orderNormal = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( stock &gt; <span class="number">0</span> )&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'普通购买，无优惠券'</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">/*接下来需要把函数包装进职责链节点，我们定义一个构造函数 Chain，在 new Chain 的时候传</span></span><br><span class="line"><span class="comment">递的参数即为需要被包装的函数，同时它还拥有一个实例属性 this.successor，表示在链中的下</span></span><br><span class="line"><span class="comment">一个节点。</span></span><br><span class="line"><span class="comment">此外 Chain 的 prototype 中还有两个函数，它们的作用如下所示：*/</span></span><br><span class="line"><span class="comment">// Chain.prototype.setNextSuccessor 指定在链中的下一个节点</span></span><br><span class="line"><span class="comment">// Chain.prototype.passRequest 传递请求给某个节点</span></span><br><span class="line"><span class="keyword">var</span> Chain = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.fn = fn; </span><br><span class="line">    <span class="keyword">this</span>.successor = <span class="literal">null</span>; </span><br><span class="line">&#125;; </span><br><span class="line">Chain.prototype.setNextSuccessor = <span class="function"><span class="keyword">function</span>(<span class="params"> successor </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.successor = successor; </span><br><span class="line">&#125;; </span><br><span class="line">Chain.prototype.passRequest = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> ret = <span class="keyword">this</span>.fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">    <span class="keyword">if</span> ( ret === <span class="string">'nextSuccessor'</span> )&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.successor &amp;&amp; <span class="keyword">this</span>.successor.passRequest.apply( <span class="keyword">this</span>.successor, <span class="built_in">arguments</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> ret; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//现在我们把 3 个订单函数分别包装成职责链的节点：</span></span><br><span class="line"><span class="keyword">var</span> chainOrder500 = <span class="keyword">new</span> Chain( order500 ); </span><br><span class="line"><span class="keyword">var</span> chainOrder200 = <span class="keyword">new</span> Chain( order200 ); </span><br><span class="line"><span class="keyword">var</span> chainOrderNormal = <span class="keyword">new</span> Chain( orderNormal ); </span><br><span class="line"><span class="comment">//然后指定节点在职责链中的顺序：</span></span><br><span class="line">chainOrder500.setNextSuccessor( chainOrder200 ); </span><br><span class="line">chainOrder200.setNextSuccessor( chainOrderNormal ); </span><br><span class="line"><span class="comment">//最后把请求传递给第一个节点：</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">1</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出：500 元定金预购，得到 100 优惠券</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">2</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出：200 元定金预购，得到 50 优惠券</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">3</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出：普通购买，无优惠券</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">1</span>, <span class="literal">false</span>, <span class="number">0</span> ); <span class="comment">// 输出：手机库存不足</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*通过改进，我们可以自由灵活地增加、移除和修改链中的节点顺序，假如某天网站运营人员</span></span><br><span class="line"><span class="comment">又想出了支持 300 元定金购买，那我们就在该链中增加一个节点即可：*/</span></span><br><span class="line"><span class="keyword">var</span> order300 = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line"> <span class="comment">// 具体实现略 </span></span><br><span class="line">&#125;; </span><br><span class="line">chainOrder300= <span class="keyword">new</span> Chain( order300 ); </span><br><span class="line">chainOrder500.setNextSuccessor( chainOrder300); </span><br><span class="line">chainOrder300.setNextSuccessor( chainOrder200); </span><br><span class="line"><span class="comment">/*对于程序员来说，我们总是喜欢去改动那些相对容易改动的地方，就像改动框架的配置文件</span></span><br><span class="line"><span class="comment">远比改动框架的源代码简单得多。在这里完全不用理会原来的订单函数代码，我们要做的只是增</span></span><br><span class="line"><span class="comment">加一个节点，然后重新设置链中相关节点的顺序。 */</span></span><br></pre></td></tr></table></figure>

<h2 id="异步职责链"><a href="#异步职责链" class="headerlink" title="异步职责链"></a>异步职责链</h2><p>在上一节的职责链模式中，我们让每个节点函数同步返回一个特定的值”nextSuccessor”，来表示 是否把请求传递给下一个节点。而在现实开发中，我们经常会遇到一些异步的问题，比如我们要在 节点函数中发起一个 ajax异步请求，异步请求返回的结果才能决定是否继续在职责链中 passRequest。 这时候让节点函数同步返回”nextSuccessor”已经没有意义了，所以要给 Chain 类再增加一个原型方法 Chain.prototype.next，表示手动传递请求给职责链中的下一个节点</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">Chain.prototype.next= <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.successor &amp;&amp; <span class="keyword">this</span>.successor.passRequest.apply( <span class="keyword">this</span>.successor, <span class="built_in">arguments</span> ); </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//下面是一个异步职责链的例子：</span></span><br><span class="line"><span class="keyword">var</span> fn1 = <span class="keyword">new</span> Chain(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="number">1</span> ); </span><br><span class="line">    <span class="keyword">return</span> <span class="string">'nextSuccessor'</span>; </span><br><span class="line">&#125;); </span><br><span class="line"><span class="keyword">var</span> fn2 = <span class="keyword">new</span> Chain(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="number">2</span> ); </span><br><span class="line">    <span class="keyword">var</span> self = <span class="keyword">this</span>; </span><br><span class="line">    setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        self.next(); </span><br><span class="line">    &#125;, <span class="number">1000</span> ); </span><br><span class="line">&#125;); </span><br><span class="line"><span class="keyword">var</span> fn3 = <span class="keyword">new</span> Chain(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="number">3</span> ); </span><br><span class="line">&#125;); </span><br><span class="line">fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 ); </span><br><span class="line">fn1.passRequest();</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">现在我们得到了一个特殊的链条，请求在链中的节点里传递，但节点有权利决定什么时候把</span></span><br><span class="line"><span class="comment">请求交给下一个节点。可以想象，异步的职责链加上命令模式（把 ajax 请求封装成命令对象，详</span></span><br><span class="line"><span class="comment">情请参考第 9 章），我们可以很方便地创建一个异步 ajax 队列库。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h2 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h2><p>职责链模式的最大优点就是解耦了请求发送者和 N 个接收者之间的复杂关 系，由于不知道链中的哪个节点可以处理你发出的请求，所以你只需把请求传递给第一个节点即 可，</p>
<p>其次，使用了职责链模式之后，链中的节点对象可以灵活地拆分重组。增加或者删除一个节 点，或者改变节点在链中的位置都是轻而易举的事情。这一点我们也已经看到，在上面的例子中， 增加一种订单完全不需要改动其他订单函数中的代码。</p>
<p>职责链模式还有一个优点，那就是可以手动指定起始节点，请求并不是非得从链中的第一个 节点开始传递。比如在公交车的例子中，如果我明确在我前面的第一个人不是售票员，那我当然 可以越过他把公交卡递给他前面的人，这样可以减少请求在链中的传递次数，更快地找到合适的 请求接受者。这在普通的条件分支语句下是做不到的，我们没有办法让请求越过某一个 if 判断。</p>
<h2 id="AOP实现"><a href="#AOP实现" class="headerlink" title="AOP实现"></a>AOP实现</h2><p>AOP是面向切面编程：面向切面的程序设计（Aspect-oriented programming，AOP，又译作面向方面的程序设计、剖面导向程序设计）是计算机科学中的一种程序设计思想，旨在将横切关注点与业务主体进行进一步分离，以提高程序代码的模块化程度。面向切面的程序设计思想也是面向切面软件开发的基础。切面的概念源于对面向对象的程序设计和计算反射的融合，但并不只限于此，它还可以用来改进传统的函数。与切面相关的编程概念还包括元对象协议、主题（Subject）、混入（Mixin）和委托（Delegate）。</p>
<p>面向切面编程为我们提供了一种将代码注入现有函数或对象的方法，而无需修改目标逻辑。</p>
<p>注入的代码虽然不是必需的，但在具有横切关注点的，例如添加日志记录功能、调试元数据、性能统计、安全控制、事务处理、异常处理或不那么通用的功能，可以在不影响原始代码的情况下注入额外的行为。把它们抽离出来，用“动态”插入的方式嵌到各业务逻辑中。业务模块可以变得比较干净，不受污染，同时这些功能点能够得到很好的复用，给模块解耦。</p>
<p>在之前的职责链实现中，我们利用了一个 Chain 类来把普通函数包装成职责链的节点。其实利用 JavaScript 的函数式特性，有一种更加方便的方法来创建职责链。 </p>
<p>下面我们改写一下 3.2.3 节 Function.prototype.after 函数，使得第一个函数返回’nextSuccessor’ 时，将请求继续传递给下一个函数，无论是返回字符串’nextSuccessor’或者 false 都只是一个约 定，当然在这里我们也可以让函数返回 false 表示传递请求，选择’nextSuccessor’字符串是因为 它看起来更能表达我们的目的，代码如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.after = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> ret = self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">        <span class="keyword">if</span> ( ret === <span class="string">'nextSuccessor'</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">return</span> ret; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> order = order500yuan.after( order200yuan ).after( orderNormal ); </span><br><span class="line">order( <span class="number">1</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出：500 元定金预购，得到 100 优惠券</span></span><br><span class="line">order( <span class="number">2</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出：200 元定金预购，得到 50 优惠券</span></span><br><span class="line">order( <span class="number">1</span>, <span class="literal">false</span>, <span class="number">500</span> ); <span class="comment">// 输出：普通购买，无优惠券</span></span><br><span class="line"><span class="comment">/*用 AOP 来实现职责链既简单又巧妙，但这种把函数叠在一起的方式，同时也叠加了函数的</span></span><br><span class="line"><span class="comment">作用域，如果链条太长的话，也会对性能有较大的影响。*/</span></span><br></pre></td></tr></table></figure>

<p>在 JavaScript 开发中，职责链模式是最容易被忽视的模式之一。实际上只要运用得当，职责 链模式可以很好地帮助我们管理代码，降低发起请求的对象和处理请求的对象之间的耦合性。职 责链中的节点数量和顺序是可以自由变化的，我们可以在运行时决定链中包含哪些节点。 无论是作用域链、原型链，还是 DOM 节点中的事件冒泡，我们都能从中找到职责链模式的 影子。职责链模式还可以和组合模式结合在一起，用来连接部件和父部件，或是提高组合对象的 效率。</p>

    </div>
    
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
        <div class="reward-container">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button onclick="var qr = document.getElementById('qr'); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
      
      <div style="display: inline-block;">
        <img src="/images/alipay.png" alt="Li Yudong 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>

        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>Li Yudong
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://example.com/2021/11/03/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%89%EF%BC%89/" title="JavaScript的设计模式（三）">http://example.com/2021/11/03/设计模式/JavaScript的设计模式（三）/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>

    
      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" rel="tag"># 设计模式</a>
          </div>
    
        

    
        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/" rel="prev" title="JavaScript的设计模式（二）">
      <i class="fa fa-chevron-left"></i> JavaScript的设计模式（二）
    </a></div>
      <div class="post-nav-item">
    <a href="/2021/11/03/%E4%B9%A6%E5%8D%95/" rel="next" title="JavaScript的设计模式（三）">
      JavaScript的设计模式（三） <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#组合模式"><span class="nav-number">2.</span> <span class="nav-text">组合模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#抽象类与透明性"><span class="nav-number">2.1.</span> <span class="nav-text">抽象类与透明性</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#注意事项"><span class="nav-number">2.2.</span> <span class="nav-text">注意事项</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#引用父对象"><span class="nav-number">2.3.</span> <span class="nav-text">引用父对象</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#总结"><span class="nav-number">2.4.</span> <span class="nav-text">总结</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#模板方法模式"><span class="nav-number">3.</span> <span class="nav-text">模板方法模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#抽象类"><span class="nav-number">3.1.</span> <span class="nav-text">抽象类</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#钩子方法"><span class="nav-number">3.2.</span> <span class="nav-text">钩子方法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#好莱坞原则"><span class="nav-number">3.3.</span> <span class="nav-text">好莱坞原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#总结-1"><span class="nav-number">3.4.</span> <span class="nav-text">总结</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#享元模式"><span class="nav-number">4.</span> <span class="nav-text">享元模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#通用结构"><span class="nav-number">4.1.</span> <span class="nav-text">通用结构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#文件上传"><span class="nav-number">4.2.</span> <span class="nav-text">文件上传</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#享元重构"><span class="nav-number">4.2.1.</span> <span class="nav-text">享元重构</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#适用性"><span class="nav-number">4.3.</span> <span class="nav-text">适用性</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#对象池"><span class="nav-number">4.4.</span> <span class="nav-text">对象池</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#职责链模式"><span class="nav-number">5.</span> <span class="nav-text">职责链模式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#简单实例"><span class="nav-number">5.1.</span> <span class="nav-text">简单实例</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#重构"><span class="nav-number">5.1.1.</span> <span class="nav-text">重构</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#可拆分节点"><span class="nav-number">5.1.2.</span> <span class="nav-text">可拆分节点</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#异步职责链"><span class="nav-number">5.2.</span> <span class="nav-text">异步职责链</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#优缺点"><span class="nav-number">5.3.</span> <span class="nav-text">优缺点</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#AOP实现"><span class="nav-number">5.4.</span> <span class="nav-text">AOP实现</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

